#pragma once

#include "http_conn.hpp"
#include <map>
#include <vector>

class HttpClient
{
public:
	typedef std::map<std::string, std::vector<HttpConn::Status>> HttpConnStatus;
public:
	HttpClient(ahttp::io_service& io) :io_(io) {}

	void set_timeout(int ms) 
	{
		time_out = ms;
	}

	void enable_auto_remove()
	{
		if (remove_timer != nullptr){
			return;
		}

		remove_timer = std::make_shared<ahttp::steady_timer>(io_, ahttp::seconds(30));
		remove_timer->async_wait(ahttp::bind(&HttpClient::remove_timer_handle, this, ahttp::placeholders::_1));
	}

	void set_ippool(const std::vector<std::string>& ips)
	{
		local_ips.clear();
		next_ip = 0;
		for (auto& ip : ips)
		{
			ahttp::error_code ec;
			auto addr = ahttp::ip::address::from_string(ip, ec);
			if (!ec)
			{
				auto ep = std::make_shared<ahttp::ip::tcp::endpoint>(addr, 0);
				local_ips.push_back(ep);
			}
		}
	}

	void set_proxy(const std::string& p) 
	{
		proxy = std::make_shared<URL>(p);
	}

	HttpConnStatus pool_status() const
	{
		HttpConnStatus ret;

		for (auto& vconn : conn_pool)
		{
			auto& v = ret[vconn.first];
			for (auto& conn : vconn.second)
			{
				v.push_back(conn->status());
			}
		}

		return ret;
	}

	void Do(HttpRequestPtr req, HttpDoneCallback cb)
	{
		auto conn = find_conn(req);
		conn->Do(req, cb);
	}

private:
	HttpConnPtr find_conn(const HttpRequestPtr& req)
	{
		std::string key = req->Url.host() + ":" + std::to_string(req->Url.port());
		HttpConnPtr conn = nullptr;
		
		auto& conns = conn_pool[key];
		for (auto it = conns.begin(); it != conns.end();)
		{
			if ((*it)->deaded()) {
				(*it)->reset_tcp_callback();
				it = conns.erase(it);
			}
			else {
				if ((*it)->available()) {
					if (conn == nullptr || conn->status().CurrentRequestCount > (*it)->status().CurrentRequestCount) {
						conn = *it;
					}
				}
				++it;
			}
		}
		

		if (conn != nullptr){
			return conn;
		}
		
		conn = std::make_shared<HttpConn>(io_);
		if (!local_ips.empty()) {
			conn->set_local_endpoint(local_ips[next_ip++ % local_ips.size()]);
		}
		conns.push_back(conn);
		conn->set_proxy(proxy);
		conn->set_timeout(time_out);
		return conn;
	}

	void remove_timer_handle(const ahttp::error_code& ec)
	{
		if (ec){
			return;
		}

		int64_t now = time(NULL);

		for (auto& vconn : conn_pool)
		{
			for (auto it = vconn.second.begin(); it != vconn.second.end();)
			{
				if ((*it)->deaded() || now - (*it)->status().LastRequestTime > 120) {
					it = vconn.second.erase(it);
				}
				else {
					++it;
				}
			}
		}

		remove_timer->expires_from_now(ahttp::seconds(30));
		remove_timer->async_wait(ahttp::bind(&HttpClient::remove_timer_handle, this, ahttp::placeholders::_1));
	}
private:
	ahttp::io_service& io_;
	std::map<std::string, std::vector<HttpConnPtr>> conn_pool;
	int time_out = 0;

	std::shared_ptr<ahttp::steady_timer> remove_timer;

	std::vector<std::shared_ptr<ahttp::ip::tcp::endpoint>> local_ips;
	int64_t next_ip = 0;
	std::shared_ptr<URL> proxy;
};